C# and any other .Net language is a managed environment. This means that, among other things, in the background a hidden process called the Garbage Collector cleans up unused memory and old references to make room for new.

Managed code is the direct responsibility of the .Net CLR which looks over things like memory management. The problem with managed code is the garbage collector process that removes unused memory locations and references and will cause other pointers to change reference as a result. For this reason pointers are only allowed within the unsafe code block.

By marking a section of code as "unsafe" you take away this responsibility from the CLR to the programmer. It is now the coders responsibility to clean up objects and memory after they are used. The advantage of this method is that code is much faster to execute as a lot of the overhead of managed code is eliminated.

Defining Unsafe Code

You are required to mark code as "unsafe" to prevent accidental use of pointers (especially from C++ programmers). Unsafe code can be marked by simply enclosing a section of code in a unsafe block:

…
unsafe
{
…
// this code is unsafe – pointers can be used.
…
}
…

Unsafe can also be used as an access modifier when declaring methods:

unsafe public static void myUnsafeMethod
{
…
// this code is unsafe – pointers can be used.
…
}

Pointer Notation

As well as specifying blocks of code as unsafe, you must also tell the compiler that you want to allow unsafe code blocks to be executed. This is done from within the project options. Under the Project menu select properties and under the Build tab there is an option to "Allow unsafe code blocks". Once this is set and you are within an unsafe code block you can use pointers. This should give you an indication of how dangerous pointers are.

Pointers are declared in the same was as in C++.

int normalInteger;
int *pointerInteger;

Note the use of an asterisk before the variable name. In this example, normalInteger is assigned the value of an integer (default = 0) , whereas pointerInteger is assigned the memory location of an integer. The asterisk can also be placed after the type, e.g. int* pointerInteger.

What is a Pointer?

A normal variable is a container that holds information of a particular type (int, string, enum and so on). A pointer variable does not hold a value or a type, but instead it contains a memory location that points to the container. When you read and write to a pointer variable you are getting and setting the memory location (also known as an address).

Pointers and Memory Locations in C#

In the above diagram we can see 5 memory locations, each with a type and a value. The size and length of the memory location will vary depending on the size of the type used, for example an integer is a 4 byte value type, so each memory location of type int is 4 bytes long.

If we declare a standard variable, e.g. int myInt = 52432, then the container identified as myInt contains the value 52432. This value cannot be changed, so we say that it is an immutable type. When you re-assign a value to it later, e.g. myInt = 89343, another int type is created and myInto points to that value. This is true for all value types.

When we declare a pointer to an int, e.g. int *myInt, we are assigning the container myInt the memory location of 4 available bytes in memory, in this case 5. In reality there are many hundreds of thousands of memory addresses which are represented in hex. For simplicity we are just using simple numbers.

If you were to assign a value to a pointer type, e.g.

int *myInt;
myInt = 2;

Then you are assigning a different memory location to the variable. In this example myInt now points to memory location 2, which is a string. Because integers are 4 bytes long, when you try and access the value of the pointer, it will only return the first 4 bytes of the string. If you assign to the pointer then the original string will become corrupt.

By changing pointers around it is very easy to corrupt or overwrite data in memory and cause the operating system to error or even crash. This is why pointers are dangerous and should only be used when it is essential to use them. There are valid reasons for using pointers, for example, applications which feature large numbers of complex mathematical operations where speed is paramount – i.e. a device driver.

Basic usage of Pointers

Pointers are used slightly different to normal variables. If you use a pointer as you would a variable then you will more than likely cause memory corruption.

unsafe
{
  int* x;
  int y = 10;
  x = &y;  
  Console.WriteLine("Value of x is: " + *x);
}

Value of x is: 10

Fixed Keyword

The Garbage Collector will re-arrange memory locations as it cleans up, causing a problem with pointers to objects being moved but the pointer still points to the old location. To overcome this problem there is a fixed keyword that can be used to pin the object and makes sure that the garbage collector will not relocate the object.

Color cl = new Color();
unsafe
{
  Console.WriteLine("Red Component of Color is: {0}", cl.R);
  fixed (byte *pRed = &cl.R)
  {
    *pRed = 122;
  }
  Console.WriteLine("Red Component of Color is: {0}", cl.R);
}

 

Tutorials in this Series